/* Autostop - camera driver
 *
 * Copyright (c) Maciej Gajewski, Grzegorz Latarowski
 * http://code.google.com/p/autostop-automaty-drogowe
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version. NO WARRANTY OF ANY KIND is expressed or implied.
 */

// kernel stuff
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/page.h>
#include <asm/kmap_types.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/pagemap.h>

// pxa specific
#include <asm/arch-pxa/pxa-regs.h>
#include <asm/arch-pxa/hardware.h>

// cmx specific. used mainly to generate compile-time error when wring kernel used
// NOTE this make drive unsuitable for cmx255. Some macro'ind had to be usedto achieve compatibilty
#include <asm/arch-pxa/cm-x270.h>

// local
#include "cameradriver.h"


// ============================================================================
// Module information
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Maciej Gajewski <maciej.gajewski0@gmail.com>");
MODULE_DESCRIPTION("Autostop's Camera driver");
MODULE_VERSION("0.1-devel " __DATE__ " " __TIME__);

// ============================================================================
// Constants

// CS lines used by camera
#define CS0	PXA_CS3_PHYS

// memory block names
#define CS0_BLOCK_NAME	"camera control registers"

// memory block size
#define IOMEM_BLOCK_SIZE	PXA_CS_SIZE

// shift between actual local bus addresz lines and FPGA address lines
# define ADDR_SHIFT			2	// 1st imposed by cmx ( only 16-bit alignmernt), 2nd by unknown bug

// actually used memory sizes
#define CS0_USED_MEMORY	2<<16	// some random value



/// device name
#define  camera_device_name "camera_ctrl"

// ============================================================================
// Module global variables

static int camera_dev_major	= CAMERA_DEV_MAJOR;		///< device major ( 0 = dynamic )
static int camera_dev_minor	= CAMERA_DEV_FIRMWARE;	///< device minor
#define camera_dev_num	1	///< camera device minor number


// flags - are blocks reserved
static char cs0_block_reserved	= 0;

// char device
struct cdev* p_device = NULL;

// memory pointers
static void* cs0	= NULL;		///< Pointer to remapped CS0 i/o region 


// ============================================================================
/// reads word from register. address is readed and value is stored to 
/// struct camera_register pointed by p_data
static int ioctl_read_regiter( void __user* p_data )
{
	// register i/o data buffer
	struct camera_register register_data;
	// copy buffer data from user
	copy_from_user( &register_data, p_data, sizeof( struct camera_register ) );
	// read data from register and copy it back to user space
	if ( cs0 ) {
		// read
		register_data.value = readw( cs0 + ( register_data.address << ADDR_SHIFT ) );
		// Say something
		//printk( KERN_INFO "ioctl read register %d, value %d\n", (int)register_data.address, (int)register_data.value );
		// copy buffer back to user
		copy_to_user( p_data, &register_data,
			 sizeof( struct camera_register ) );
	
		return 0;
	}

	// major error!
	printk( KERN_ALERT " camera: fatal: can not read register: io memory not mapped\n" );
	
	return -EBADF;
}

// ============================================================================
/// reads data from register multiple times, copies hi-order bytes to user-supplied buffer.
/// p_data points to struct camera_multiple_bytes
static int ioctl_read_hibytes( void __user* p_data )
{
	// all variables
	int bytes_to_read;// actual butes to read
	unsigned long p_user_buffer;// user pointer
	// count pages
	unsigned long end;
	unsigned long start;
	int pages_num;	// pages num
	unsigned long offset;	// first page offset (all other starts with zero )

	void* p_register;		// register addr

	// register i/o data buffer
	struct camera_multiple_bytes register_data;

	// copy buffer data from user
	copy_from_user( &register_data, p_data, sizeof( struct camera_multiple_bytes ) );

	// get info on user buffer
	bytes_to_read	= register_data.count;								// actual butes to read
	p_user_buffer	= (unsigned long)register_data.buffer;				// user pointer
	// count pages
	end				= (p_user_buffer + bytes_to_read + PAGE_SIZE - 1) >> PAGE_SHIFT;
	start			= p_user_buffer >> PAGE_SHIFT;
	pages_num		= end - start;
	offset			= p_user_buffer & ~PAGE_MASK;

	p_register = cs0 + ( register_data.address << ADDR_SHIFT );

	// TODO in future:
	/*
	// User attempted Overflow!
	if ((uaddr + count) < uaddr)
	{
		return -EBADF;
	}
	*/


	// read data from register and copy it back to user space
	if ( cs0 ) {
		
		// memory pages info
		struct page* pages[ pages_num ];
		int res = 0;
		int page_index = 0;	// loop index

		// map user memory
		down_read(&current->mm->mmap_sem);	// take lock on the memory map
		/* rw==READ means read from drive, write into memory area */
		res = get_user_pages(
			current,
			current->mm,
			p_user_buffer,
			pages_num,
			1, /* write (non-zero means write) */
			0, /* don't force */
			pages,
			NULL
			);
		up_read(&current->mm->mmap_sem);	// release lock
		

		// check result
		if ( res != pages_num )
		{
			printk( KERN_ALERT " camera: fatal: couldn't get user pages\n");
			return -EBADF;
		}
		
		// now do rocket-fast copy!
		while( bytes_to_read > 0 )
		{
			// bytes to read in this iteration
			int i;
			int to_read		= bytes_to_read > ( PAGE_SIZE - offset) ? ( PAGE_SIZE - offset) : bytes_to_read;
			uint8_t* pkaddr	= kmap_atomic( pages[ page_index ], KM_USER0 ) + offset; 

			bytes_to_read -= to_read;

			// read directly into user space
			for( i = 0; i < to_read; ++i )
			{
				*(pkaddr++)	= (uint8_t) ( readw( p_register ) >> 8 );
			}

			// move user's buffer pointer
			p_user_buffer += to_read;

			// unmap kernel page
			kunmap_atomic( pkaddr, KM_USER0 );

			// mark page as dirty
			SetPageDirty( pages[ page_index ] );

			// get next page
			page_index ++;
			offset = 0;
		}
		// put user pages
		for ( page_index = 0 ; page_index < pages_num; page_index++ )
		{
			page_cache_release( pages[ page_index ] );
		}

		return 0;



/*
		// init buffer
		const int read_buffer_size	= 4096;
		uint8_t buffer[ read_buffer_size ];
		int bytes_to_read			= register_data.count;
		void __user* p_user_buffer	= register_data.buffer;
		void* p_register = cs0 + ( register_data.address << ADDR_SHIFT );
		
		// read all data
		while( bytes_to_read > 0 )
		{
			// bytes to read in this iteration
			int i;
			int to_read = bytes_to_read > read_buffer_size ? read_buffer_size : bytes_to_read;
			uint8_t* p = buffer;

			bytes_to_read -= to_read;

			// read
			for( i = 0; i < to_read; ++i )
			{
				*(p++)	= (uint8_t) ( readw( p_register ) >> 8 );
			}

			// copy buffer back to user
			copy_to_user( p_user_buffer, buffer, to_read );

			// move user's buffer pointer
			p_user_buffer += to_read;
		}
	
		return 0;
		*/
	}

	// major error!
	printk( KERN_ALERT " camera: fatal: can not read register: io memoryn ot mapped\n" );
	
	return -EBADF;
}

// ==================================================================
// ============================================================================
/// reads data from register multiple times
/// p_data points to struct camera_multiple_bytes
static int ioctl_read_multi( void __user* p_data )
{
	// all variables
	int bytes_to_read;// actual butes to read
	unsigned long p_user_buffer;// user pointer

	void* p_register;		// register addr

	// register i/o data buffer
	struct camera_multiple_bytes register_data;

	// copy buffer data from user
	copy_from_user( &register_data, p_data, sizeof( struct camera_multiple_bytes ) );

	// get info on user buffer
	bytes_to_read	= register_data.count;								// actual butes to read
	p_user_buffer	= (unsigned long)register_data.buffer;				// user pointer

	p_register = cs0 + ( register_data.address << ADDR_SHIFT );

	// read data from register and copy it back to user space
	if ( cs0 )
	{
		// init buffer
		const int read_buffer_size	= PAGE_SIZE;
		uint8_t buffer[ read_buffer_size ];
		int bytes_to_read			= register_data.count;
		void __user* p_user_buffer	= register_data.buffer;
		void* p_register = cs0 + ( register_data.address << ADDR_SHIFT );
		
		// read all data
		while( bytes_to_read > 0 )
		{
			// bytes to read in this iteration
			int to_read = bytes_to_read > read_buffer_size ? read_buffer_size : bytes_to_read;
			bytes_to_read -= to_read;

			// read
			readsw( p_register, buffer, to_read >> 1 ); // to_Read shifted couse we read 16-bit values

			// copy buffer back to user
			copy_to_user( p_user_buffer, buffer, to_read );

			// move user's buffer pointer
			p_user_buffer += to_read;
		}
	
		return 0;
	}

	// major error!
	printk( KERN_ALERT " camera: fatal: can not read register: io memory not mapped\n" );
	
	return -EBADF;
}

// ============================================================================
/// writes word to register. address and value are readed from  
/// struct camera_register pointed by p_data
/// return 0 on ok orerro code
static int ioctl_write_regiter( void __user* p_data )
{
	// register i/o data buffer
	struct camera_register register_data;
	// copy buffer data from user
	copy_from_user( &register_data, p_data, sizeof( struct camera_register ) );
	// Say something
	//printk( KERN_INFO "ioctl write register %d, value %d\n", (int)register_data.address, (int)register_data.value );
	// write register
	if ( cs0 ) {
		writew( register_data.value, cs0 + ( register_data.address << ADDR_SHIFT ) );
		return 0;
	}

	// major error!
	printk( KERN_ALERT " camera: fatal: can not write register: io memory not mapped\n" );
	
	return -EBADF;
}

// ============================================================================
// File operations

// ============================================================================
/// The 'open' implementation
int camera_open( struct inode *p_inode, struct file *p_file )
{
	// check file pointer
	if ( ! p_file ) {
		printk( KERN_ALERT " file NULL!\n" );
		return -1;
	}
	// check mode
	if ( p_file->f_mode & FMODE_READ & FMODE_WRITE ) {
		printk( KERN_ALERT " camera: read/write mode not supported\n" );
		return -1;
	}
	
	// only wor writing:
	if ( p_file->f_mode & FMODE_WRITE ) {
		// TODO writing only code here
	}
	// do nothing for reading
	
	return 0;
}

// ============================================================================
/// The 'release' implementation
int camera_release( struct inode *p_inode, struct file *p_file )
{
	// check file pointer
	if ( ! p_file ) {
		printk( KERN_ALERT " file NULL!\n" );
		return -1;
	}
	
	// only for writing:
	if ( p_file->f_mode & FMODE_WRITE ) {
		// TODO writing only code here
	}
	// do nothing in read mode
	
	return 0;
}

// ============================================================================
// the 'write' implementation
ssize_t camera_write( struct file *p_file, const char __user * p_data, size_t size, loff_t* p_offset )
{
	// do nothing
	return size; // pretend we've written all
}

// ============================================================================
/// The 'read' implementation. Returns status in human/shell readable format
ssize_t camera_read( struct file *p_file, char __user * p_data, size_t size, loff_t* p_offset )
{
	// do nothing
	return size; // pretend to read all
}


// ============================================================================
/// The 'ioctl' implementation
int camera_ioctl( struct inode *p_inode, struct file *p_file, unsigned int command, unsigned long param )
{
	// Say something
	//printk( KERN_INFO "camera ioctl: %d, %d\n", (int)command, (int)param );
	
	// process commands
	switch( command ) {
	
	case CAMERA_CS0_READ:
		return ioctl_read_regiter( (void __user*) param );
	case CAMERA_CS0_WRITE:
		return ioctl_write_regiter( (void __user*) param );
	
	case CAMERA_CS0_READ_HIBYTES:
		return ioctl_read_hibytes( (void __user*) param );
	
	case CAMERA_CS0_READ_MULTI:
		return ioctl_read_multi( (void __user*) param );

	default:
		printk( KERN_INFO " camera: unsupported ioct: %d, %d\n", (int)command, (int)param );
		return -ENOTTY;
	}
	
	return 0;
}

// ============================================================================
/// camera file operations
struct file_operations camera_fops = {
	.owner		= THIS_MODULE,
	.write		= camera_write,
	.read		= camera_read,
	.open		= camera_open,
	.release	= camera_release,
	.ioctl		= camera_ioctl
};

// ============================================================================
/// Module deinitilaization. Frees resources allocated by init
static void camera_exit(void)
{
	dev_t dev = MKDEV( camera_dev_major, camera_dev_minor );
	
	// say goodbye
	printk( KERN_ALERT "Removing camera driver\n");
	
	// unregister device
	if ( p_device ) {
		printk( KERN_INFO " Unregistering device\n" );
		cdev_del( p_device );
	}
	// unmap memory
	if ( cs0 ) {
		printk( KERN_INFO " Unmapping CS0 memory\n" );
		iounmap( cs0 );
	}
	// free reserved i/o memory
	if ( cs0_block_reserved ) {
		printk( KERN_INFO " Freeing CS0 io memory block\n" );
		release_mem_region( CS0, IOMEM_BLOCK_SIZE );
	}
	
	// free allocated device major
	if ( camera_dev_major ) {
		printk( KERN_INFO " Freeing allocated device major: %d\n", camera_dev_major );
		unregister_chrdev_region( dev, camera_dev_num );
	}
}

// ============================================================================
/// Module initialization.
/// Task performed:
/// - allocarte device major/minor numbers
/// - setting GPIO_WRITE and GPIO_PROGRAM gpio pins into output mode
/// - setting CS lines into CS mode (alternate function)
/// - reserve i/o memory blocks
static int camera_init( void )
{
	dev_t dev = 0;
	int result;
	
	// say 'hello'!
	printk( KERN_ALERT "Camera driver loaded\n" );
	
	// general initilaization
	
	// allocate device major
	if ( camera_dev_major ) {
		printk( KERN_INFO " Reserving device major: %d\n", camera_dev_major );
		dev = MKDEV( camera_dev_major, camera_dev_minor );
		result = register_chrdev_region( dev, camera_dev_num,
				camera_device_name );
	} else {
		printk( KERN_INFO " Requesting dynamic device major nuber...\n" );
		result = alloc_chrdev_region( &dev, camera_dev_major, camera_dev_num,
				camera_device_name );
		camera_dev_major = MAJOR( dev );
		printk( KERN_INFO " Device major got: %d\n", camera_dev_major );
		
	}
	if ( result < 0 ) {
		printk( KERN_ALERT " FATAL: Could not allocate device major: %d\n", camera_dev_major );
		return result;
	}

	// set-up gpio for chip-select lines
	pxa_gpio_mode( GPIO79_nCS_3_MD ); // FIXME: depends on module (x255 uses CS2, x270 uses CS3)

	// reserve i/o memory  block for CS0
	printk( KERN_INFO " Reserving i/o memory blocks...\n" );
	if ( request_mem_region( CS0, IOMEM_BLOCK_SIZE, CS0_BLOCK_NAME) ) {
		cs0_block_reserved = 1;
		printk( KERN_INFO " CS0 block reserved\n" );
	}
	else {
		printk( KERN_ALERT " FATAL: could not reserve CS0 (%x) block!\n", CS0 );
		camera_exit();
		return -1;
	}
	
	// map memory regions
	cs0 = ioremap( CS0, CS0_USED_MEMORY );
	printk( KERN_INFO " Mapping CS0 to memoty at %p\n", cs0 );
	
	// configure bus for nCS0
	//MSC1 = ( MSC1 & 0x0000ffff ) | ( 0x0464 << 16 ); // TODO test. this is how we're do it?
	
	// register char device
	printk( KERN_INFO " Registering char device...\n" );
	p_device = cdev_alloc();
	if ( p_device ) {
		p_device->ops = &camera_fops;
		if ( cdev_add( p_device, dev, camera_dev_num ) ) {
			printk( KERN_INFO " FATAL: Could not register device\n" );
			camera_exit();
			return -1;
		} else {
			printk( KERN_INFO " Device registered\n" );
		}
	} else {
		printk( KERN_INFO " FATAL: Could not allocate device\n" );
		camera_exit();
		return -1;
	}
	
	return 0;
}

// ============================================================================
// registering local routines
module_init(camera_init);
module_exit(camera_exit);

// eof

